/**  
* Copyright (c) 2008, Noel Lynch All rights reserved. Redistribution and use  
* in source and binary forms, with or without modification, are permitted  
* provided that the following conditions are met: Redistributions of source  
* code must retain the above copyright notice, this list of conditions and the  
* following disclaimer. Redistributions in binary form must reproduce the above  
* copyright notice, this list of conditions and the following disclaimer in the  
* documentation and/or other materials provided with the distribution. Neither  
* the name of Noel Lynch nor the names of contributors may be used to  
* endorse or promote products derived from this software without specific prior  
* written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR  
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
// Nov 20, 2008

package com.sceneworker.editors.trimesheditor;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Triangle;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.TriMesh;
import com.sceneworker.editors.trimesheditor.OrthoViewPanel.OrthoMeshViewMenu;

public class View3DPanel extends JComponent implements MouseMotionListener {

	protected static final float NEAR_PLANE = 200f;
	
	static	final	public	float		kTolerance = 1f;
	
	// tri mesh to render
	protected		TriMesh		mcl_triMesh;
	
	// last point for drag events
	private		Point		mcl_lastPoint = null;
	
	// zoom for screen space render
	protected		float		mf_zoom = 1f;
	
	// render only front facing polys
	protected		boolean		mb_renderFrontFaceOnly;
	
	// the original direction the eye is looking
	final	protected		Vector3f	kEyeInitialDirection = new Vector3f(0, 0, -1);
	
	// eye direction adjusted
	protected		Vector3f		mcl_eyeDir = new Vector3f();
	
	// eye rotation quat
	protected		Quaternion		mcl_eyeRot = new Quaternion();
	
	// eye translation
	protected		Vector3f		mcl_eyeTx = new Vector3f();
	
	// center halved
	protected		Vector2f		mcl_screenCenterHalfed = new Vector2f();
	
	public	View3DPanel(TriMesh cl_tm) {
		mcl_triMesh = cl_tm;
		
		final	View3DPanel		lcl_this = this;
		
		mcl_eyeTx.z = 10;
		
		addMouseMotionListener(this);
		
		addMouseListener(new MouseAdapter() {

			@Override
			public void mouseReleased(MouseEvent cl_e) {
				mcl_lastPoint = null;
				
				if(cl_e.isPopupTrigger()) {
					MeshViewMenu	lcl_popUp = new MeshViewMenu(lcl_this);
					lcl_popUp.show(lcl_this, cl_e.getX(), cl_e.getY());
				}
			}

			@Override
			public void mouseClicked(MouseEvent e) {
				requestFocusInWindow();
			}

			@Override
			public void mousePressed(MouseEvent cl_e) {
				super.mousePressed(cl_e);
				
				if(cl_e.isPopupTrigger()) {
					MeshViewMenu	lcl_popUp = new MeshViewMenu(lcl_this);
					lcl_popUp.show(lcl_this, cl_e.getX(), cl_e.getY());
				}
			}
		});
		
		addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent cl_e) {
				if(cl_e.getKeyChar() == 'a') {
					mcl_eyeTx.x -= 0.1f;
				}
				
				if(cl_e.getKeyChar() == 'd') {
					mcl_eyeTx.x += 0.1f;
				}
				
				if(cl_e.getKeyChar() == 'w') {
					mcl_eyeTx.z += 0.1f;
				}
				
				if(cl_e.getKeyChar() == 's') {
					mcl_eyeTx.z -= 0.1f;
				}
				
				if(cl_e.getKeyChar() == 'q') {
					mcl_eyeTx.y += 0.1f;
				}
				
				if(cl_e.getKeyChar() == 'z') {
					mcl_eyeTx.y -= 0.1f;
				}
				
				repaint();
			}
			
		});
		
		requestFocusInWindow();
	}
	
	
	@Override
	public Dimension getPreferredSize() {
		return new Dimension(300, 300);
	}

	public	void	setRenderFrontFaceOnly(boolean b_on) {
		mb_renderFrontFaceOnly = b_on;
	}
	
	public	void	setZoom(float f_z) {
		mf_zoom = f_z;
		repaint();
	}
	
	@Override
	protected void paintComponent(Graphics cl_g) {
		Graphics2D		lcl_g = (Graphics2D)cl_g.create();
		
		lcl_g.setColor(Color.lightGray);
		lcl_g.fillRect(0, 0, getWidth(), getHeight());
		
		lcl_g.setColor(Color.red);
		lcl_g.drawLine(0, getHeight() >> 1, getWidth(), getHeight() >> 1);
		lcl_g.drawLine(getWidth() >> 1, 0, getWidth() >> 1, getHeight());
		
		mcl_screenCenterHalfed.x = (getWidth() >> 1);
		mcl_screenCenterHalfed.y = (getHeight() >> 1);
		
		FloatBuffer		lcla_vertices = mcl_triMesh.getVertexBuffer();
		IntBuffer		lcla_indices = mcl_triMesh.getIndexBuffer();
		FloatBuffer		lcla_colourBuffer = mcl_triMesh.getColorBuffer();
		
		Vector3f		lcl_a = new Vector3f();
		Vector3f		lcl_b = new Vector3f();
		Vector3f		lcl_c = new Vector3f();
		ColorRGBA		lcl_clrA = new ColorRGBA();
		ColorRGBA		lcl_clrB = new ColorRGBA();
		ColorRGBA		lcl_clrC = new ColorRGBA();
		Triangle		lcl_tri = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());
		
		float			lf_temp;
		int				li_temp;
		float			lf_dot = -1;
		
		for(int li_index = 0; li_index < lcla_indices.capacity(); li_index += 3) {
			li_temp = lcla_indices.get(li_index) * 3;
			
			lcl_a.x = lcla_vertices.get(li_temp);
			lcl_a.y = lcla_vertices.get(li_temp + 1);
			lcl_a.z = lcla_vertices.get(li_temp + 2);
			
			if(lcla_colourBuffer != null) {
				lcl_clrA.r = lcla_colourBuffer.get(li_temp);
				lcl_clrA.g = lcla_colourBuffer.get(li_temp + 1);
				lcl_clrA.g = lcla_colourBuffer.get(li_temp + 2);
				lcl_clrA.a = lcla_colourBuffer.get(li_temp + 3);
			}
			
			li_temp = lcla_indices.get(li_index + 1) * 3;
			
			lcl_b.x = lcla_vertices.get(li_temp);
			lcl_b.y = lcla_vertices.get(li_temp + 1);
			lcl_b.z = lcla_vertices.get(li_temp + 2);
			
			if(lcla_colourBuffer != null) {
				lcl_clrB.r = lcla_colourBuffer.get(li_temp);
				lcl_clrB.g = lcla_colourBuffer.get(li_temp + 1);
				lcl_clrB.g = lcla_colourBuffer.get(li_temp + 2);
				lcl_clrB.a = lcla_colourBuffer.get(li_temp + 3);
			}
			
			li_temp = lcla_indices.get(li_index + 2) * 3;
			
			lcl_c.x = lcla_vertices.get(li_temp);
			lcl_c.y = lcla_vertices.get(li_temp + 1);
			lcl_c.z = lcla_vertices.get(li_temp + 2);
			
			if(lcla_colourBuffer != null) {
				lcl_clrC.r = lcla_colourBuffer.get(li_temp);
				lcl_clrC.g = lcla_colourBuffer.get(li_temp + 1);
				lcl_clrC.g = lcla_colourBuffer.get(li_temp + 2);
				lcl_clrC.a = lcla_colourBuffer.get(li_temp + 3);
			}
			
			// if any pt behind eye plane ignore this triangle
			if(toEyeSpace(lcl_a)) {
				if(toEyeSpace(lcl_b)) {
					if(toEyeSpace(lcl_c)) {
						lf_dot = -1;
						if(mb_renderFrontFaceOnly) {
							lcl_tri.set(0, lcl_a);
							lcl_tri.set(1, lcl_b);
							lcl_tri.set(2, lcl_c);
							lcl_tri.calculateNormal();
							lf_dot = lcl_tri.getNormal().dot(kEyeInitialDirection);
						}
						
						if(lf_dot <= 0) {
							projectToImageSpace(lcl_a);
							projectToScreenSpace(lcl_a);
							projectToImageSpace(lcl_b);
							projectToScreenSpace(lcl_b);
							projectToImageSpace(lcl_c);
							projectToScreenSpace(lcl_c);
							
							cl_g.setColor(new Color(lcl_clrA.r, lcl_clrA.g, lcl_clrA.b));
							cl_g.drawLine((int)lcl_a.x, (int)lcl_a.y, (int)lcl_b.x, (int)lcl_b.y);

							cl_g.setColor(new Color(lcl_clrB.r, lcl_clrB.g, lcl_clrB.b));
							cl_g.drawLine((int)lcl_b.x, (int)lcl_b.y, (int)lcl_c.x, (int)lcl_c.y);

							cl_g.setColor(new Color(lcl_clrC.r, lcl_clrC.g, lcl_clrC.b));
							cl_g.drawLine((int)lcl_c.x, (int)lcl_c.y, (int)lcl_a.x, (int)lcl_a.y);
						}

					}
				}
			}
		}
		
		cl_g.setColor(Color.BLACK);
		cl_g.setFont(new Font("Helvetica", Font.PLAIN, 11));
		cl_g.drawString(String.format("tx %.2f %.2f %.2f", mcl_eyeTx.x, mcl_eyeTx.y, mcl_eyeTx.z), 0, 20);
		float		lfa_angles[] = mcl_eyeRot.toAngles(null);
		cl_g.drawString(String.format("rot %.2f %.2f %.2f", lfa_angles[0] * FastMath.RAD_TO_DEG, 
				lfa_angles[1] * FastMath.RAD_TO_DEG, 
				lfa_angles[2] * FastMath.RAD_TO_DEG), 0, 10);
		
		lcl_g.dispose();
	}
	
	public void mouseDragged(MouseEvent cl_e) {
		if(mcl_lastPoint == null) { 
			mcl_lastPoint = cl_e.getPoint();
			return;
		}
		
		Point		lcl_pt = cl_e.getPoint();
		
		float			lf_yDelta = (float)(mcl_lastPoint.x - lcl_pt.x);
		
		Quaternion		lcl_rotY = new Quaternion();
		lcl_rotY.fromAngleAxis(lf_yDelta * 0.1f, Vector3f.UNIT_Y);
		mcl_eyeRot.multLocal(lcl_rotY);
				
		float			lf_xDelta = (float)(mcl_lastPoint.y - lcl_pt.y);
		
		Quaternion		lcl_rotX = new Quaternion();
		lcl_rotX.fromAngleAxis(lf_xDelta * 0.1f, Vector3f.UNIT_X);
		mcl_eyeRot.multLocal(lcl_rotX);
				
		// update eye view
		mcl_eyeDir = mcl_eyeRot.mult(kEyeInitialDirection);
		
		mcl_lastPoint = lcl_pt;
		repaint();
	}

	public void mouseMoved(MouseEvent arg0) {
	}
	
	class	MeshViewMenu extends JPopupMenu {
		public	View3DPanel		mcl_viewPanel;
		
		public	MeshViewMenu(View3DPanel cl_par) {
			mcl_viewPanel = cl_par;
			
			JMenu			lcl_zoomMenu = new JMenu("Zoom");
			add(lcl_zoomMenu);
			
			buildZoomMenuItem(0.01f, lcl_zoomMenu);
			buildZoomMenuItem(0.05f, lcl_zoomMenu);
			buildZoomMenuItem(0.1f, lcl_zoomMenu);
			buildZoomMenuItem(0.25f, lcl_zoomMenu);
			buildZoomMenuItem(0.5f, lcl_zoomMenu);
			buildZoomMenuItem(1f, lcl_zoomMenu);
			buildZoomMenuItem(2f, lcl_zoomMenu);
			buildZoomMenuItem(4f, lcl_zoomMenu);
			buildZoomMenuItem(10f, lcl_zoomMenu);
			buildZoomMenuItem(20f, lcl_zoomMenu);
			buildZoomMenuItem(50f, lcl_zoomMenu);
			
			final	JCheckBoxMenuItem		lcl_renderFF = new JCheckBoxMenuItem("Render Front Face Only");
			lcl_renderFF.setState(mcl_viewPanel.mb_renderFrontFaceOnly);
			lcl_renderFF.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					mcl_viewPanel.mb_renderFrontFaceOnly = lcl_renderFF.getState();
					mcl_viewPanel.repaint();
				}
			});
			add(lcl_renderFF);
			
			JMenuItem		lcl_reset = new JMenuItem("Reset View");
			lcl_reset.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					mcl_viewPanel.mcl_eyeRot = new Quaternion();
					mcl_viewPanel.mcl_eyeDir = new Vector3f(kEyeInitialDirection);
					mcl_viewPanel.repaint();
				}
			});
			add(lcl_reset);
			
		}
		
		private		void	buildZoomMenuItem(final float f_z, JMenu cl_zoom) {
			JMenuItem		lcl_mi = new JMenuItem((int)(f_z * 100f) + "%");
			lcl_mi.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					mcl_viewPanel.setZoom(f_z);
				}
			});
			cl_zoom.add(lcl_mi);
		}
	}
	
	final	private	boolean	toEyeSpace(Vector3f cl_pt) {
		transformToEye(cl_pt);
		
		if(cl_pt.z >= 0) {
			// out of eye plane
			return false;
		}
		
		return true;
	}
	
	final	private		void	transformToEye(Vector3f cl_pt) {
		cl_pt = mcl_eyeRot.mult(cl_pt, cl_pt);
		
		cl_pt.subtractLocal(mcl_eyeTx);
	}

	final	private		void	projectToImageSpace(Vector3f cl_pt) {
		cl_pt.x = (cl_pt.x * NEAR_PLANE) / cl_pt.z;
		cl_pt.y = (cl_pt.y * NEAR_PLANE) / cl_pt.z;
	}
	
	final	private		void	projectToScreenSpace(Vector3f cl_pt) {
		cl_pt.x = (mcl_screenCenterHalfed.x + (cl_pt.x * mf_zoom));
		cl_pt.y = (mcl_screenCenterHalfed.y + (cl_pt.y * mf_zoom));
	}
	

}
